msg_tool\scripts\silky/
map.rs

1use crate::ext::io::*;
2use crate::scripts::base::*;
3use crate::types::*;
4use crate::utils::encoding::*;
5use anyhow::Result;
6use std::io::{Seek, SeekFrom};
7
8#[derive(Debug)]
9/// A builder for Silky Engine map scripts.
10pub struct MapBuilder {}
11
12impl MapBuilder {
13    /// Creates a new `MapBuilder`.
14    pub fn new() -> Self {
15        Self {}
16    }
17}
18
19impl ScriptBuilder for MapBuilder {
20    fn default_encoding(&self) -> Encoding {
21        Encoding::Utf16LE
22    }
23
24    fn build_script(
25        &self,
26        buf: Vec<u8>,
27        _filename: &str,
28        encoding: Encoding,
29        _archive_encoding: Encoding,
30        config: &ExtraConfig,
31        _archive: Option<&Box<dyn Script>>,
32    ) -> Result<Box<dyn Script>> {
33        Ok(Box::new(Map::new(buf, encoding, config)?))
34    }
35
36    fn extensions(&self) -> &'static [&'static str] {
37        &["map"]
38    }
39
40    fn script_type(&self) -> &'static ScriptType {
41        &ScriptType::SilkyMap
42    }
43
44    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
45        let reader = MemReaderRef::new(&buf[..buf_len]);
46        try_parse(reader).ok()
47    }
48
49    fn can_create_file(&self) -> bool {
50        true
51    }
52
53    fn create_file<'a>(
54        &'a self,
55        filename: &'a str,
56        writer: Box<dyn WriteSeek + 'a>,
57        encoding: Encoding,
58        file_encoding: Encoding,
59        config: &ExtraConfig,
60    ) -> Result<()> {
61        create_file(
62            filename,
63            writer,
64            encoding,
65            file_encoding,
66            config.custom_yaml,
67        )
68    }
69}
70
71fn create_file<'a>(
72    custom_filename: &'a str,
73    mut writer: Box<dyn WriteSeek + 'a>,
74    encoding: Encoding,
75    output_encoding: Encoding,
76    yaml: bool,
77) -> Result<()> {
78    let input = crate::utils::files::read_file(custom_filename)?;
79    let s = decode_to_string(output_encoding, &input, true)?;
80    let strings: Vec<String> = if yaml {
81        serde_yaml_ng::from_str(&s)?
82    } else {
83        serde_json::from_str(&s)?
84    };
85    writer.write_u32(strings.len() as u32)?;
86    let header_len = 8 * strings.len();
87    writer.seek_relative(header_len as i64)?;
88    let mut offsets = Vec::with_capacity(strings.len());
89    for s in strings {
90        offsets.push(writer.stream_position()? as u32);
91        let buf = if encoding.is_utf16le() {
92            let mut buf = encode_string(encoding, &s, false)?;
93            buf.extend_from_slice(&[0, 0]);
94            buf
95        } else {
96            let mut buf = encode_string(encoding, &s, false)?;
97            buf.push(0);
98            buf
99        };
100        writer.write_all(&buf)?;
101    }
102    writer.seek(SeekFrom::Start(4))?;
103    for (i, offset) in offsets.iter().enumerate() {
104        writer.write_u32(i as u32)?;
105        writer.write_u32(*offset)?;
106    }
107    Ok(())
108}
109
110#[derive(Debug)]
111/// A Silky Engine map script.
112struct Map {
113    strings: Vec<String>,
114    custom_yaml: bool,
115}
116
117impl Map {
118    /// Creates a new `Map` from the given buffer and encoding.
119    pub fn new(buf: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
120        let mut data = MemReader::new(buf);
121        let count = data.read_u32()?;
122        let mut strings = Vec::with_capacity(count as usize);
123        for _ in 0..count {
124            let _index = data.read_u32()?;
125            let offset = data.read_u32()?;
126            if encoding.is_utf16le() {
127                let data = data.peek_u16string_at(offset as u64)?;
128                let s = decode_to_string(encoding, &data, true)?;
129                strings.push(s);
130            } else {
131                let data = data.peek_cstring_at(offset as u64)?;
132                let s = decode_to_string(encoding, data.as_bytes(), true)?;
133                strings.push(s);
134            }
135        }
136        Ok(Self {
137            strings,
138            custom_yaml: config.custom_yaml,
139        })
140    }
141}
142
143impl Script for Map {
144    fn default_output_script_type(&self) -> OutputScriptType {
145        OutputScriptType::Json
146    }
147
148    fn is_output_supported(&self, _: OutputScriptType) -> bool {
149        true
150    }
151
152    fn default_format_type(&self) -> FormatOptions {
153        FormatOptions::None
154    }
155
156    fn custom_output_extension<'a>(&'a self) -> &'a str {
157        if self.custom_yaml { "yaml" } else { "json" }
158    }
159
160    fn extract_messages(&self) -> Result<Vec<Message>> {
161        let mut messages = Vec::with_capacity(self.strings.len());
162        for s in &self.strings {
163            messages.push(Message::new(s.replace("\\n", "\n"), None));
164        }
165        Ok(messages)
166    }
167
168    fn import_messages<'a>(
169        &'a self,
170        messages: Vec<Message>,
171        mut file: Box<dyn WriteSeek + 'a>,
172        _filename: &str,
173        encoding: Encoding,
174        replacement: Option<&'a ReplacementTable>,
175    ) -> Result<()> {
176        if messages.len() != self.strings.len() {
177            return Err(anyhow::anyhow!(
178                "The number of messages does not match. (expected {}, got {})",
179                self.strings.len(),
180                messages.len()
181            ));
182        }
183        file.write_u32(messages.len() as u32)?;
184        let header_len = 8 * messages.len();
185        file.seek_relative(header_len as i64)?;
186        let mut offsets = Vec::with_capacity(messages.len());
187        for msg in messages {
188            let mut m = msg.message.clone();
189            if let Some(table) = replacement {
190                for (k, v) in &table.map {
191                    m = m.replace(k, v);
192                }
193            }
194            m = m.replace("\n", "\\n");
195            offsets.push(file.stream_position()? as u32);
196            let buf = if encoding.is_utf16le() {
197                let mut buf = encode_string(encoding, &m, false)?;
198                buf.extend_from_slice(&[0, 0]);
199                buf
200            } else {
201                let mut buf = encode_string(encoding, &m, false)?;
202                buf.push(0);
203                buf
204            };
205            file.write_all(&buf)?;
206        }
207        file.seek(SeekFrom::Start(4))?;
208        for (i, offset) in offsets.iter().enumerate() {
209            file.write_u32(i as u32)?;
210            file.write_u32(*offset)?;
211        }
212        Ok(())
213    }
214
215    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
216        let s = if self.custom_yaml {
217            serde_yaml_ng::to_string(&self.strings)?
218        } else {
219            serde_json::to_string_pretty(&self.strings)?
220        };
221        let s = encode_string(encoding, &s, false)?;
222        let mut file = crate::utils::files::write_file(filename)?;
223        file.write_all(&s)?;
224        Ok(())
225    }
226
227    fn custom_import<'a>(
228        &'a self,
229        custom_filename: &'a str,
230        file: Box<dyn WriteSeek + 'a>,
231        encoding: Encoding,
232        output_encoding: Encoding,
233    ) -> Result<()> {
234        create_file(
235            custom_filename,
236            file,
237            encoding,
238            output_encoding,
239            self.custom_yaml,
240        )
241    }
242}
243
244fn try_parse(mut r: MemReaderRef) -> Result<u8> {
245    let count = r.read_u32()?;
246    let index = r.read_u32()?;
247    if index != 0 {
248        return Err(anyhow::anyhow!("Invalid index"));
249    }
250    let mut prv_offset = r.read_u32()?;
251    if prv_offset < 4 + 8 * count {
252        return Err(anyhow::anyhow!("Invalid offset"));
253    }
254    let tlen = r.data.len();
255    for i in 1..count {
256        if r.pos + 8 > tlen {
257            break;
258        }
259        let index = r.read_u32()?;
260        if index != i {
261            return Err(anyhow::anyhow!("Invalid index"));
262        }
263        let offset = r.read_u32()?;
264        if offset <= prv_offset {
265            return Err(anyhow::anyhow!("Invalid offset"));
266        }
267        prv_offset = offset;
268    }
269    Ok(if (r.pos - 4) / 8 < 100 { 10 } else { 20 })
270}